home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-04-25 | 68.4 KB | 2,640 lines |
- #import "GraphicView.h"
- #import "Rectangle.h"
- #import "Group.h"
- #import "Image.h"
- #import "draw.h"
- #import <appkit/NXCursor.h>
- #import <appkit/NXColorWell.h>
- #import <appkit/Matrix.h>
- #import <appkit/Panel.h>
- #import <appkit/Pasteboard.h>
- #import <appkit/Text.h>
- #import <appkit/defaults.h>
- #import <appkit/nextstd.h>
- #import <appkit/tiff.h>
- #import <appkit/timer.h>
- #import <dpsclient/wraps.h>
- #import <objc/List.h>
- #import <string.h>
- #import <math.h>
- #import <zone.h>
- #import <mach.h>
- #import <sys/param.h>
-
- /* This file is best read in a window as wide as this comment (so that this comment fits on one line). */
-
- #define Notify(title, msg) NXRunAlertPanel(title, msg, NULL, NULL, NULL)
-
- const char *DrawPboardType = "Draw Graphic List Type";
-
- BOOL InMsgPrint = NO; /* whether we are in msgPrint: */
-
- #define LEFTARROW 172
- #define RIGHTARROW 174
- #define UPARROW 173
- #define DOWNARROW 175
-
- @implementation GraphicView : View
- /*
- * The GraphicView class is the core of a DrawDocument.
- *
- * It overrides the View methods related to drawing and event handling
- * and allows manipulation of Graphic objects.
- *
- * The user is allowed to select objects, move them around, group and
- * ungroup them, change their font, and cut and paste them to the pasteboard.
- * It understands multiple formats including PostScript and TIFF as well as
- * its own internal format. The GraphicView can also import PostScript and
- * TIFF documents and manipulate them as Graphic objects.
- *
- * This is a very skeleton implementation and is intended purely for
- * example purposes. It should be very easy to add new Graphic objects
- * merely by subclassing the Graphic class. No code need be added to the
- * GraphicView class when a new Graphic subclass is added.
- *
- * Moving is accomplished using a selection cache which is shared among
- * all instances of GraphicView in the application. The objects in the
- * selection are drawn using opaque ink on a transparent background so
- * that when they are moved around, the user can see through them to the
- * objects that are not being moved.
- *
- * All of the drawing is done in an off-screen window which is merely
- * composited back to the screen. This makes for very fast redraw of
- * areas obscured either by the selection moving or the user's scrolling.
- *
- * The glist instance variable is just an ordered list of all the Graphics
- * in the GraphicView. The slist is an ordered list of the Graphic objects
- * in the selection. cacheWindow is the off-screen window into which the
- * objects are drawn. Flags: dirty indicates that the user has modified
- * the collection of objects since the bit was last cleared; grid is the
- * distance between pixels in the grid imposed on drawing; cacheing is used
- * so that drawSelf:: knows when to composite from the off-screen cache and
- * when to draw into the off-screen cache; groupInSlist is used to keep
- * track of whether a Group Graphic is in the slist so that it knows when
- * to highlight the Ungroup entry in the menu.
- *
- * This class should be able to be used outside the context of this
- * application since it takes great pains not to depend on any other objects
- * in the application.
- */
-
- /*
- * Of course, one should NEVER use global variables in an application, but
- * the following is necessary. DrawStatus is
- * analogous to the Application Kit's NXDrawingStatus and reflects whether
- * we are in some modal loop. By definition, that modal loop is "atomic"
- * since we own the mouse during its duration (of course, all bets are off
- * if we have multiple mice!).
- */
-
- DrawStatusType DrawStatus = Normal; /* global state reflecting what we
- are currently doing (resizing, etc.) */
-
- const float DEFAULT_GRID_GRAY = 0.8333;
-
- static id currentGraphic = nil; /* won't be used if NXApp knows how
- to keep track of the currentGraphic */
-
- static float KeyMotionDeltaDefault = 0.0;
-
- /* Private C functions needed to implement methods in this class. */
-
- static id createCache(NXSize *size, NXZone *zone)
- /*
- * Creates an appropriately size off-screen window to cache bits in.
- */
- {
- NXRect cacheRect;
-
- cacheRect.origin.x = 0.0;
- cacheRect.origin.y = 0.0;
- cacheRect.size = *size;
-
- return [[[Window allocFromZone:zone] initContent:&cacheRect
- style:NX_PLAINSTYLE
- backing:NX_RETAINED
- buttonMask:0
- defer:NO] reenableDisplay];
- }
-
- /* Code-cleaning macros */
-
- #define stopTimer(timer) if (timer) { \
- NXEndTimer(timer); \
- timer = NULL; \
- }
-
- #define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.1);
-
- #define GRID (gvFlags.gridDisabled ? 1.0 : (NXCoord)gvFlags.grid)
-
- #define grid(point) \
- (point).x = floor(((point).x / GRID) + 0.5) * GRID; \
- (point).y = floor(((point).y / GRID) + 0.5) * GRID;
-
- #define DIRTY(condition) \
- if (condition && !gvFlags.dirty) { \
- gvFlags.dirty = YES; \
- [window setDocEdited:YES]; \
- }
-
- static void getRegion(NXRect *region, const NXPoint *p1, const NXPoint *p2)
- /*
- * Returns the rectangle which has p1 and p2 as its corners.
- */
- {
- region->size.width = p1->x - p2->x;
- region->size.height = p1->y - p2->y;
- if (region->size.width < 0.0) {
- region->origin.x = p2->x + region->size.width;
- region->size.width = ABS(region->size.width);
- } else {
- region->origin.x = p2->x;
- }
- if (region->size.height < 0.0) {
- region->origin.y = p2->y + region->size.height;
- region->size.height = ABS(region->size.height);
- } else {
- region->origin.y = p2->y;
- }
- }
-
- static BOOL checkForGroup(id list)
- /*
- * Looks through the given list searching for objects of the Group class.
- * We use this to keep the gvFlags.groupInSlist flag up to date when things
- * are removed from the slist (the list of selected objects). That way
- * we can efficiently keep the Ungroup menu item up to date.
- */
- {
- int i = [list count];
- while (i--) if ([[list objectAt:i] isKindOf:[Group class]]) return YES;
- return NO;
- }
-
- /* Hack to support growable Text objects. */
-
- static id createEditView(GraphicView *self)
- /*
- * editView is essentially a dumb, FLIPPED (with extra emphasis on the
- * flipped) subview of our GraphicView which completely covers it and
- * which automatically sizes itself to always completely cover the
- * GraphicView. It is necessary since growable Text objects only work
- * when they are subviews of a flipped view.
- *
- * See TextGraphic for more details about why we need editView
- * (it is purely a workaround for a limitation of the Text object).
- */
- {
- id view;
-
- [self setAutoresizeSubviews:YES];
- view = [[View allocFromZone:[self zone]] initFrame:&(self->frame)];
- [view setFlipped:YES];
- [view setAutosizing:NX_WIDTHSIZABLE|NX_HEIGHTSIZABLE];
- [self addSubview:view];
-
- return view;
- }
-
- /* Routines to check the types in the Pasteboard */
-
- static BOOL matchPasteType(const NXAtom *types, NXAtom type)
- {
- if (types) while (*types) if (*types++ == type) return YES;
- return NO;
- }
-
- static NXAtom drawPasteType(const NXAtom *types)
- /*
- * Returns the pasteboard type in the passed list of types which is preferred
- * by the Draw program for pasting. The Draw program prefers its own type
- * of course, then it prefers PostScript over TIFF.
- */
- {
- if (matchPasteType(types, DrawPboardType)) return DrawPboardType;
- if (matchPasteType(types, NXPostScriptPboardType)) return NXPostScriptPboardType;
- if (matchPasteType(types, NXTIFFPboardType)) return NXTIFFPboardType;
- return NULL;
- }
-
- /* Factory methods. */
-
- + initialize
- /*
- * We up the version of the class so that we can read old .draw files.
- * See the read: method for how we use the version.
- */
- {
- [self setVersion:1];
- DrawPboardType = NXUniqueStringNoCopy(DrawPboardType);
- return self;
- }
-
- /* Lazy Pasteboard evaluation handler */
-
- /*
- * IMPORTANT: The pasteboard:provideData: method is a factory method since the
- * factory object is persistent and there is no guarantee that the INSTANCE of
- * GraphicView that put the Draw format into the Pasteboard will be around
- * to lazily put PostScript or TIFF in there, so we keep one around (actually
- * we only create it when we need it) to do the conversion (scrapper).
- *
- * If you find this part of the code confusing, then you need not even
- * use the provideData: mechanism--simply put the data for all the different
- * types your program knows how to put in the Pasteboard in at the time
- * that you declareTypes:.
- */
-
- + convert:(NXTypedStream *)ts to:(const char *)type using:(SEL)writer toPasteboard:pb
- /*
- * Converts the data in the Pasteboard from Draw internal format to
- * either PostScript or TIFF using the writeTIFFToStream: and writePSToStream:
- * methods. It sends these messages to the scrapper (a GraphicView cached
- * to perform this very function). Note that the scrapper view is put in
- * a window, but that window is off-screen, has no backing store, and no
- * title (and is thus very cheap).
- */
- {
- id w;
- char *data;
- NXZone *zone;
- NXStream *stream;
- int length, maxlen;
- GraphicView *scrapper;
- const NXRect scrapperFrame = {{0.0, 0.0}, {11.0*72.0, 14.0*72.0}};
-
- if (!ts) return self;
-
- zone = NXCreateZone(vm_page_size, vm_page_size, NO);
- NXNameZone(zone, "Scrapper");
- scrapper = [[GraphicView allocFromZone:zone] initFrame:&scrapperFrame];
- w = [[Window allocFromZone:zone] initContent:&scrapperFrame
- style:NX_PLAINSTYLE
- backing:NX_NONRETAINED
- buttonMask:0
- defer:NO];
- [w reenableDisplay];
- [w setContentView:scrapper];
-
- stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
- NXSetTypedStreamZone(ts, zone);
- scrapper->glist = NXReadObject(ts);
- [scrapper perform:writer with:(id)stream];
- NXGetMemoryBuffer(stream, &data, &length, &maxlen);
- [pb writeType:type data:data length:length];
- NXCloseMemory(stream, NX_FREEBUFFER);
- [w free];
- NXDestroyZone(zone);
-
- return self;
- }
-
-
- + pasteboard:sender provideData:(const char *)type
- /*
- * Called by the Pasteboard whenever PostScript or TIFF data is requested
- * from the Pasteboard by some other application. The current contents of
- * the Pasteboard (which is in the internal format) is taken out and loaded
- * into a stream, then convert:to:using:toPasteboard: is called.
- */
- {
- char *data;
- int length;
- NXStream *stream;
- NXTypedStream *ts;
-
- if (type) {
- [sender readType:DrawPboardType data:&data length:&length];
- stream = NXOpenMemory(data, length, NX_READONLY);
- if (stream) {
- ts = NXOpenTypedStream(stream, NX_READONLY);
- if (ts) {
- if (type == NXPostScriptPboardType) {
- [self convert:ts to:NXPostScriptPboardType using:@selector(writePSToStream:) toPasteboard:sender];
- } else if (type == NXTIFFPboardType) {
- [self convert:ts to:NXTIFFPboardType using:@selector(writeTIFFToStream:) toPasteboard:sender];
- }
- NXCloseTypedStream(ts);
- }
- NXCloseMemory(stream, NX_FREEBUFFER);
- }
- }
-
- return self;
- }
-
- /* Creation methods. */
-
- static void initClassVars()
- /*
- * Sets up any default values and cursors.
- */
- {
- static BOOL registered = NO;
- const char *validSendTypes[4];
- const char *validReturnTypes[4];
-
- if (!KeyMotionDeltaDefault) {
- const char *value = NXGetDefaultValue([NXApp appName], "KeyMotionDelta");
- if (value) KeyMotionDeltaDefault = atof(value);
- KeyMotionDeltaDefault = MAX(KeyMotionDeltaDefault, 1.0);
- }
- if (!registered) {
- registered = YES;
- validSendTypes[0] = NXPostScriptPboardType;
- validSendTypes[1] = NXTIFFPboardType;
- validSendTypes[2] = DrawPboardType;
- validSendTypes[3] = NULL;
- validReturnTypes[0] = NXPostScriptPboardType;
- validReturnTypes[1] = NXTIFFPboardType;
- validReturnTypes[2] = DrawPboardType;
- validReturnTypes[3] = NULL;
- [NXApp registerServicesMenuSendTypes:validSendTypes andReturnTypes:validReturnTypes];
- }
- }
-
- - initFrame:(const NXRect *)frameRect
- /*
- * Initializes the view's instance variables.
- * This view is considered important enough to allocate it a gstate.
- */
- {
- [super initFrame:frameRect];
- glist = [[List allocFromZone:[self zone]] init];
- slist = [[List allocFromZone:[self zone]] init];
- cacheWindow = createCache(&bounds.size, [self zone]);
- gvFlags.grid = 1;
- [self allocateGState];
- gridGray = DEFAULT_GRID_GRAY;
- PSInit();
- currentGraphic = [Rectangle class]; /* default graphic */
- currentGraphic = [self currentGraphic]; /* trick to allow NXApp to control currentGraphic */
- editView = createEditView(self);
- initClassVars();
- return self;
- }
-
- /* Free method */
-
- - free
- {
- if (gupCoords) {
- NX_FREE(gupCoords);
- NX_FREE(gupOps);
- NX_FREE(gupBBox);
- }
- [glist freeObjects];
- [glist free];
- [slist free];
- [cacheWindow free];
- if (![editView superview]) [editView free];
- return [super free];
- }
-
- /* Services menu methods */
-
- - validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
- /*
- * We are a valid requestor whenever any of the send or
- * return types is PostScript, TIFF, or Draw.
- * Note that we cache around the NXAtom for each
- * type since it is important this this method be fast
- * (it gets called relatively often).
- */
- {
-
- if ((!sendType || !*sendType ||
- ((sendType == NXPostScriptPboardType ||
- sendType == NXTIFFPboardType ||
- sendType == DrawPboardType) && [slist count])) &&
- (!returnType || !*returnType ||
- returnType == NXPostScriptPboardType ||
- returnType == NXTIFFPboardType ||
- returnType == DrawPboardType)) {
- return self;
- }
-
- return [super validRequestorForSendType:sendType andReturnType:returnType];
- }
-
- - (BOOL)writeSelectionToPasteboard:pboard types:(NXAtom *)types
- /*
- * If one of the requested types is one of the ones we handle,
- * then we put our selection in the Pasteboard.
- */
- {
- while (types && *types) {
- if (*types == NXPostScriptPboardType || *types == NXTIFFPboardType || *types == DrawPboardType) break;
- types++;
- }
-
- if (types && *types && [self copyToPasteboard:pboard types:types]) {
- serviceActsOnSelection = YES;
- return YES;
- } else {
- return NO;
- }
- }
-
- - readSelectionFromPasteboard:pboard
- /*
- * When a result comes back from the Services menu request,
- * we replace the selection with the return value.
- * If the user really wants the return value in addition to
- * the current selection, she can simply copy, then paste
- * twice to get two copies, then choose the Services menu item.
- */
- {
- if (serviceActsOnSelection) [self delete:self];
- serviceActsOnSelection = NO;
- [[self pasteFromPasteboard:pboard] free];
- return self;
- }
-
- /* Private selection management methods. */
-
- - selectionCache
- /*
- * Shares an off-screen window used to draw the selection in so that it
- * can be dragged around. If the current off-screen window is equal in
- * size or larger than the passed size, then it is simply returned.
- * Otherwise, it is resized to be the specified size.
- */
- {
- NXRect rect;
- static id selectioncache = nil;
-
- if (!selectioncache) {
- rect = bounds;
- selectioncache = [Window newContent:&rect
- style:NX_PLAINSTYLE
- backing:NX_RETAINED
- buttonMask:0
- defer:NO];
- [selectioncache reenableDisplay];
- } else {
- [selectioncache getFrame:&rect];
- if (rect.size.width < bounds.size.width || rect.size.height < bounds.size.height) {
- [selectioncache sizeWindow:MAX(rect.size.width, bounds.size.width)
- :MAX(rect.size.height, bounds.size.height)];
- }
- }
-
- return selectioncache;
- }
-
- - getBBox:(NXRect *)bbox of:list extended:(BOOL)extended
- /*
- * Returns a rectangle which encloses all the objects in the list.
- */
- {
- int i;
- NXRect eb;
-
- i = [list count];
- if (i) {
- if (extended) {
- [[list objectAt:--i] getExtendedBounds:bbox];
- while (i--) NXUnionRect([[list objectAt:i] getExtendedBounds:&eb], bbox);
- } else {
- [[list objectAt:--i] getBounds:bbox];
- while (i--) {
- [[list objectAt:i] getBounds:&eb];
- NXUnionRect(&eb, bbox);
- }
- }
- } else {
- bbox->size.width = bbox->size.height = 0.0;
- }
-
- return self;
- }
-
- - getBBox:(NXRect *)bbox of:list
- {
- return [self getBBox:bbox of:list extended:YES];
- }
-
- - recacheSelection
- /*
- * Redraws the selection in the off-screen cache (not the selection cache),
- * then composites it back on to the screen.
- */
- {
- NXRect sbounds;
-
- if ([slist count]) {
- [self getBBox:&sbounds of:slist];
- gvFlags.cacheing = YES;
- [self drawSelf:&sbounds :1];
- gvFlags.cacheing = NO;
- [self display:&sbounds :1];
- }
-
- return self;
- }
-
- - getSelection
- /*
- * Resets slist by going through the glist and locating all the Graphics
- * which respond YES to the isSelected method.
- */
- {
- id g;
- int i;
-
- [slist free];
- slist = [[List allocFromZone:[self zone]] init];
- gvFlags.groupInSlist = NO;
- i = [glist count];
- while (i--) {
- g = [glist objectAt:i];
- if ([g isSelected]) {
- [slist insertObject:g at:0];
- gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
- }
- }
-
- return self;
- }
-
- - saveSelection
- {
- [saveList free];
- saveList = [slist copy];
- return self;
- }
-
- - restoreSelection
- {
- int i;
- id g;
-
- [slist free];
- slist = [[List allocFromZone:[self zone]] init];
- gvFlags.groupInSlist = NO;
- i = [saveList count];
- while (i--) {
- g = [saveList objectAt:i];
- [g select];
- [slist insertObject:g at:0];
- gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
- }
- return self;
- }
-
- - compositeSelection:(const NXRect *)sbounds from:(int)gstate
- /*
- * Composites from the specified gstate whatever part of sbounds is
- * currently visible in the View.
- */
- {
- PScomposite(0.0, 0.0, NX_WIDTH(sbounds), NX_HEIGHT(sbounds),
- gstate, NX_X(sbounds), NX_Y(sbounds), NX_SOVER);
- [window flushWindow];
- NXPing();
- return self;
- }
-
- - (int)cacheSelection
- /*
- * Caches the selection into the application-wide selection cache
- * window (a window which has alpha in it). See also: selectionCache:.
- * It draws the objects without their knobbies in the selection cache,
- * but it leaves them selected. Returns the gstate of the selection
- * cache.
- */
- {
- int i;
- NXRect sbounds;
- id selectionCache;
-
- [self getBBox:&sbounds of:slist];
- selectionCache = [self selectionCache];
- [[selectionCache contentView] lockFocus];
- PSsetgray(NX_WHITE);
- PSsetalpha(0.0); /* fully transparent */
- PStranslate(- sbounds.origin.x, - sbounds.origin.y);
- sbounds.size.width += 1.0;
- sbounds.size.height += 1.0;
- NXRectFill(&sbounds);
- sbounds.size.width -= 1.0;
- sbounds.size.height -= 1.0;
- PSsetalpha(1.0); /* fully opaque */
- i = [slist count];
- while (i--) [[[[slist objectAt:i] deselect] draw:NULL] select];
- [Graphic showFastRectFills];
- PStranslate(sbounds.origin.x, sbounds.origin.y);
- [[selectionCache contentView] unlockFocus];
-
- return [selectionCache gState];
- }
-
- /* Other private methods. */
-
- - cacheGraphic:graphic
- /*
- * Draws the graphic into the off-screen cache, then composites
- * it back to the screen.
- * NOTE: This ONLY works if the graphic is on top of the list!
- * That is why it is a private method ...
- */
- {
- NXRect eb;
-
- [[cacheWindow contentView] lockFocus];
- [graphic draw:NULL];
- [Graphic showFastRectFills];
- [[cacheWindow contentView] unlockFocus];
- [self display:[graphic getExtendedBounds:&eb] :1];
-
- return self;
- }
-
- - resetGUP
- /*
- * The "GUP" is the Grid User Path. It is a user path which draws a grid
- * the size of the bounds of the GraphicView. This gets called whenever
- * the View is resized or the grid spacing is changed. It sets up all
- * the arguments to DPSDoUserPath() called in drawSelf::.
- */
- {
- int x, y, i, j;
- short w, h;
-
- if (gvFlags.grid < 4) return self;
-
- x = (int)bounds.size.width / gvFlags.grid;
- y = (int)bounds.size.height / gvFlags.grid;
- gupLength = (x << 2) + (y << 2);
- if (gupCoords) {
- NX_FREE(gupCoords);
- NX_FREE(gupOps);
- NX_FREE(gupBBox);
- }
- NX_ZONEMALLOC([self zone], gupCoords, short, gupLength);
- NX_ZONEMALLOC([self zone], gupOps, char, gupLength >> 1);
- NX_ZONEMALLOC([self zone], gupBBox, short, 4);
- w = bounds.size.width;
- h = bounds.size.height;
- j = 0;
- for (i = 1; i <= y; i++) {
- gupCoords[j++] = 0.0;
- gupCoords[j++] = i * gvFlags.grid;
- gupCoords[j++] = w;
- gupCoords[j] = gupCoords[j-2];
- j++;
- }
- for (i = 1; i <= x; i++) {
- gupCoords[j++] = i * gvFlags.grid;
- gupCoords[j++] = 0.0;
- gupCoords[j] = gupCoords[j-2];
- j++;
- gupCoords[j++] = h;
- }
- i = gupLength >> 1;
- while (i) {
- gupOps[--i] = dps_lineto;
- gupOps[--i] = dps_moveto;
- }
- gupBBox[0] = gupBBox[1] = 0;
- gupBBox[2] = bounds.size.width + 1;
- gupBBox[3] = bounds.size.height + 1;
-
- return self;
- }
-
- #define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK
-
- - (BOOL)move:(NXEvent *)event
- /*
- * Moves the selection by cacheing the selected graphics into the
- * selection cache, then compositing them repeatedly as the user
- * moves the mouse. The tracking loop uses TIMER events to autoscroll
- * at regular intervals. TIMER events do not have valid mouse coordinates,
- * so the last coordinates are saved and restore when there is a TIMER event.
- */
- {
- int gstate;
- NXEvent peek;
- NXCoord dx, dy;
- NXTrackingTimer *timer = NULL;
- NXPoint p, start, last, sboundspad;
- NXRect minbounds, sbounds, visibleRect;
- BOOL canScroll, tracking = YES, alternate, horizConstrain = NO, vertConstrain = NO;
-
- last = event->location;
- alternate = (event->flags & NX_ALTERNATEMASK) ? YES : NO;
-
- event = [NXApp getNextEvent:MOVE_MASK];
- if (event->type == NX_MOUSEUP) return NO;
-
- // PShidecursor();
-
- [self convertPoint:&last fromView:nil];
- [self grid:&last];
-
- [self lockFocus];
-
- gstate = [self cacheSelection];
- [self graphicsPerform:@selector(deactivate) andDraw:YES];
- [self getBBox:&sbounds of:slist];
- [self getBBox:&minbounds of:slist extended:NO];
- sboundspad.x = minbounds.origin.x - sbounds.origin.x;
- sboundspad.y = minbounds.origin.y - sbounds.origin.y;
- [self compositeSelection:&sbounds from:gstate];
-
- [self getVisibleRect:&visibleRect];
- canScroll = !NXEqualRect(&visibleRect, &bounds);
-
- start = sbounds.origin;
-
- while (tracking) {
- p = event->location;
- [self convertPoint:&p fromView:nil];
- [self grid:&p];
- dx = p.x - last.x;
- dy = p.y - last.y;
- if (dx || dy) {
- [self drawSelf:&sbounds :1];
- if (alternate && (dx || dy)) {
- if (ABS(dx) > ABS(dy)) {
- horizConstrain = YES;
- dy = 0.0;
- } else {
- vertConstrain = YES;
- dx = 0.0;
- }
- alternate = NO;
- } else if (horizConstrain) {
- dy = 0.0;
- } else if (vertConstrain) {
- dx = 0.0;
- }
- NXOffsetRect(&sbounds, dx, dy);
- minbounds.origin.x = sbounds.origin.x + sboundspad.x;
- minbounds.origin.y = sbounds.origin.y + sboundspad.y;
- [self tryToPerform:@selector(updateRulers:) with:(void *)&minbounds];
- if (!canScroll || NXContainsRect(&visibleRect, &sbounds)) {
- [self compositeSelection:&sbounds from:gstate];
- stopTimer(timer);
- }
- last = p;
- }
- tracking = (event->type != NX_MOUSEUP);
- if (tracking) {
- if (canScroll && !NXContainsRect(&visibleRect, &sbounds)) {
- [window disableFlushWindow];
- [self scrollRectToVisible:&sbounds];
- [self getVisibleRect:&visibleRect];
- [self compositeSelection:&sbounds from:gstate];
- [[window reenableFlushWindow] flushWindow];
- startTimer(timer);
- }
- p = event->location;
- if (![NXApp peekNextEvent:MOVE_MASK into:&peek]) {
- event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK];
- } else {
- event = [NXApp getNextEvent:MOVE_MASK];
- }
- if (event->type == NX_TIMER) event->location = p;
- }
- }
-
- if (canScroll) stopTimer(timer);
-
- // PSshowcursor();
-
- p.x = sbounds.origin.x - start.x;
- p.y = sbounds.origin.y - start.y;
- if (p.x || p.y) [self graphicsPerform:@selector(moveBy:) with:(id)&p andDraw:NO];
-
- [self graphicsPerform:@selector(activate) andDraw:YES];
-
- [self tryToPerform:@selector(updateRulers:) with:nil];
-
- [window flushWindow];
- [self unlockFocus];
-
- return YES;
- }
-
- #define DRAG_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK)
-
- - dragSelect:(NXEvent *)event
- /*
- * Allows the user the drag out a box to select all objects either
- * intersecting the box, or fully contained within the box (depending
- * on the state of the ALTERNATE key). After the selection is made,
- * the slist is updated.
- */
- {
- id g;
- int i;
- NXPoint p, last, start;
- NXTrackingTimer *timer = NULL;
- BOOL mustContain, shift, canScroll;
- NXRect visibleRect, eb, region, oldRegion;
-
- p = start = event->location;
- [self convertPoint:&start fromView:nil];
- last = start;
-
- shift = (event->flags & NX_SHIFTMASK) ? YES : NO;
- mustContain = (event->flags & NX_ALTERNATEMASK) ? YES : NO;
-
- [self lockFocus];
-
- [self getVisibleRect:&visibleRect];
- canScroll = !NXEqualRect(&visibleRect, &bounds);
- if (canScroll) startTimer(timer);
-
- PSsetgray(NX_LTGRAY);
- PSsetlinewidth(0.0);
-
- event = [NXApp getNextEvent:DRAG_MASK];
- while (event->type != NX_MOUSEUP) {
- if (event->type == NX_TIMER) event->location = p;
- p = event->location;
- [self convertPoint:&p fromView:nil];
- if (p.x != last.x || p.y != last.y) {
- getRegion(®ion, &p, &start);
- NXInsetRect(&oldRegion, -1.0, -1.0);
- [window disableFlushWindow];
- [self drawSelf:&oldRegion :1];
- if (canScroll) {
- [self scrollRectToVisible:®ion];
- [self scrollPointToVisible:&p];
- }
- PSrectstroke(region.origin.x, region.origin.y, region.size.width, region.size.height);
- [self tryToPerform:@selector(updateRulers:) with:(void *)®ion];
- [[window reenableFlushWindow] flushWindow];
- oldRegion = region;
- last = p;
- NXPing();
- }
- p = event->location;
- event = [NXApp getNextEvent:DRAG_MASK];
- }
-
- if (canScroll) stopTimer(timer);
-
- for (i = [glist count] - 1; i >= 0; i--) {
- g = [glist objectAt:i];
- [g getExtendedBounds:&eb];
- if (![g isLocked] && ![g isSelected] &&
- ((mustContain && NXContainsRect(®ion, &eb)) ||
- (!mustContain && NXIntersectsRect(®ion, &eb)))) {
- [slist insertObject:[g select] at:0];
- gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
- }
- }
-
- NXInsetRect(®ion, -1.0, -1.0);
- [self drawSelf:®ion :1];
- [self recacheSelection];
-
- [self tryToPerform:@selector(updateRulers:) with:nil];
-
- [self unlockFocus];
-
- return self;
- }
-
- /* Public interface methods. */
-
- - dirty
- {
- gvFlags.dirty = YES;
- [window setDocEdited:YES];
- return self;
- }
-
- - (BOOL)isDirty
- {
- return gvFlags.dirty;
- }
-
- - (BOOL)isEmpty
- {
- return [glist count] == 0;
- }
-
- - (BOOL)hasEmptySelection
- {
- return [slist count] == 0;
- }
-
- - graphicsPerform:(SEL)aSelector andDraw:(BOOL)flag
- /*
- * Performs the given aSelector on each member of the slist, then
- * recaches and redraws the larger of the area covered by the objects before
- * the selector was applied and the area covered by the objects after the
- * selector was applied. If you want to perform a method on each item
- * in the slist and NOT redraw, then use the List method makeObjectsPerform:.
- */
- {
- id g;
- int i, count;
- NXRect eb, affectedBounds;
-
- if (flag) {
- count = [slist count];
- if (count) {
- [[slist objectAt:0] getExtendedBounds:&affectedBounds];
- for (i = 1; i < count; i++) {
- g = [slist objectAt:i];
- NXUnionRect([g getExtendedBounds:&eb], &affectedBounds);
- }
- for (i = 0; i < count; i++) {
- g = [slist objectAt:i];
- [g perform:aSelector];
- NXUnionRect([g getExtendedBounds:&eb], &affectedBounds);
- }
- [self cache:&affectedBounds];
- }
- } else {
- [slist makeObjectsPerform:aSelector];
- }
-
- return self;
- }
-
- - graphicsPerform:(SEL)aSelector with:(void *)argument andDraw:(BOOL)flag
- {
- id g;
- int i, count;
- NXRect eb, affectedBounds;
-
- if (flag) {
- count = [slist count];
- if (count) {
- [[slist objectAt:0] getExtendedBounds:&affectedBounds];
- for (i = 1; i < count; i++) {
- g = [slist objectAt:i];
- NXUnionRect([g getExtendedBounds:&eb], &affectedBounds);
- }
- for (i = 0; i < count; i++) {
- g = [slist objectAt:i];
- [g perform:aSelector with:argument];
- NXUnionRect([g getExtendedBounds:&eb], &affectedBounds);
- }
- [self cache:&affectedBounds];
- }
- } else {
- [slist makeObjectsPerform:aSelector with:argument];
- }
-
- return self;
- }
-
- - graphicsPerformSingle:(SEL)aSelector with:(void *)argument on:(id) g
- {
- NXRect affectedBounds;
-
- [g getExtendedBounds:&affectedBounds];
- [g perform:aSelector with:argument];
- [self cache:&affectedBounds];
-
- return self;
- }
-
- - cache:(const NXRect *)rect
- /*
- * Draws all the Graphics intersected by rect into the off-screen cache,
- * then composites the rect back to the screen (but does NOT flushWindow).
- */
- {
- gvFlags.cacheing = YES;
- [self drawSelf:rect :1];
- gvFlags.cacheing = NO;
- if ([self canDraw]) {
- [self lockFocus];
- [self drawSelf:rect :1];
- [self unlockFocus];
- }
- return self;
- }
-
- - insertGraphic:graphic
- /*
- * Inserts the specified graphic into the glist and draws it.
- * The new graphic will join the selection, not replace it.
- */
- {
- NXRect eb;
-
- if (graphic) {
- [slist insertObject:graphic at:0];
- [glist insertObject:graphic at:0];
- [self cache:[graphic getExtendedBounds:&eb]];
- if ([graphic isKindOf:[Group class]]) gvFlags.groupInSlist = YES;
- [window flushWindow];
- DIRTY(YES);
- }
-
- return self;
- }
-
- - insertGraphicNoSelect:graphic
- /*
- * Inserts the specified graphic into the glist and draws it.
- */
- {
- NXRect eb;
-
- if (graphic) {
- [graphic deselect];
- [glist insertObject:graphic at:0];
- [self cache:[graphic getExtendedBounds:&eb]];
- [window flushWindow];
- DIRTY(YES);
- }
-
- return self;
- }
-
- - removeGraphic:graphic
- /*
- * Removes the graphic from the GraphicView and redraws.
- */
- {
- int i;
- NXRect eb;
- id g = nil;
-
- if (!graphic) return self;
-
- i = [glist count];
- while (g != graphic && i--) g = [glist objectAt:i];
- if (g == graphic) {
- [g getExtendedBounds:&eb];
- [glist removeObjectAt:i];
- [slist removeObject:g];
- if ([g isKindOf:[Group class]]) gvFlags.groupInSlist = checkForGroup(slist);
- [self cache:&eb];
- [window flushWindow];
- DIRTY(YES);
- [NXApp delayedFree:g];
- }
-
- return self;
- }
-
- - selectedGraphic
- /*
- * If there is one and only one Graphic selected, this method returns it.
- */
- {
- if ([slist count] == 1) {
- id g = [slist objectAt:0];
- return [g isKindOf:[Group class]] ? nil : g;
- } else {
- return nil;
- }
- }
-
- /* Methods to modify the grid of the GraphicView. */
-
- - (int)gridSpacing
- {
- return gvFlags.gridDisabled ? 1 : gvFlags.grid;
- }
-
- - (BOOL)gridIsVisible
- {
- return gvFlags.showGrid;
- }
-
- - (BOOL)gridIsEnabled
- {
- return !gvFlags.gridDisabled;
- }
-
- - (float)gridGray
- {
- return gridGray;
- }
-
- - setGridSpacing:(int)gridSpacing
- {
- if (gridSpacing != gvFlags.grid && gridSpacing > 0 && gridSpacing < 256) {
- gvFlags.grid = gridSpacing;
- if (gvFlags.showGrid) {
- [self resetGUP];
- [self cache:&bounds];
- [window flushWindow];
- }
- }
- return self;
- }
-
- - setGridEnabled:(BOOL)flag
- {
- gvFlags.gridDisabled = flag ? NO : YES;
- return self;
- }
-
- - setGridVisible:(BOOL)flag
- {
- if (gvFlags.showGrid != flag) {
- gvFlags.showGrid = flag;
- if (flag) [self resetGUP];
- [self cache:&bounds];
- [window flushWindow];
- }
- return self;
- }
-
- - setGridGray:(float)gray
- {
- if (gray != gridGray) {
- gridGray = gray;
- if (gvFlags.showGrid) {
- [self cache:&bounds];
- [window flushWindow];
- }
- }
- return self;
- }
-
- - setGridSpacing:(int)gridSpacing andGray:(float)gray
- {
- if (gray != gridGray || (gridSpacing != gvFlags.grid && gridSpacing > 0 && gridSpacing < 256)) {
- gridGray = gray;
- if (gvFlags.grid != gridSpacing && gridSpacing > 0 && gridSpacing < 256) {
- gvFlags.grid = gridSpacing;
- if (gvFlags.showGrid) [self resetGUP];
- }
- if (gvFlags.showGrid) {
- [self cache:&bounds];
- [window flushWindow];
- }
- }
- return self;
- }
-
- - grid:(NXPoint *)p
- {
- grid(*p);
- return self;
- }
-
- /* Public methods for importing foreign data types into the GraphicView */
-
- - placeGraphic:graphic at:(const NXPoint *)location
- /*
- * Places the graphic centered at the given location on the page.
- * If the graphic is too big, the user is asked whether the graphic
- * should be scaled.
- */
- {
- int scale;
- NXPoint offset;
- float sx, sy, factor;
- NXRect gbounds, myBounds;
-
- if (graphic) {
- [graphic getExtendedBounds:&gbounds];
- if (gbounds.size.width > bounds.size.width || gbounds.size.height > bounds.size.height) {
- scale = NXRunAlertPanel("Load Image",
- "The image is too large to fit on the page. Scale it to fit?",
- "Scale", "Don't Scale", "Cancel");
- if (scale < 0) {
- [graphic free];
- return self;
- } else if (scale > 0) {
- sx = (bounds.size.width / gbounds.size.width) * 0.95;
- sy = (bounds.size.height / gbounds.size.height) * 0.95;
- factor = MIN(sx, sy);
- gbounds.size.width *= factor;
- gbounds.size.height *= factor;
- [graphic sizeTo:&gbounds.size];
- }
- }
- if (location) [graphic centerAt:location];
- [graphic getExtendedBounds:&gbounds];
- myBounds = bounds;
- NXContainRect(&myBounds, &gbounds);
- offset.x = bounds.origin.x - myBounds.origin.x;
- offset.y = bounds.origin.y - myBounds.origin.y;
- if (offset.x || offset.y) [graphic moveBy:&offset];
- [self deselectAll:self];
- [self insertGraphic:graphic];
- [self scrollGraphicToVisible:graphic];
- }
-
- return graphic;
- }
-
- - loadImageFromStream:(NXStream *)stream at:(const NXPoint *)location allowAlpha:(BOOL)alphaOk
- /*
- * Creates a new Image object using the PostScript or TIFF found in the
- * given stream and inserts it as the only item in the selection (i.e.
- * it deslects everything else). The new Graphic is centered at the
- * given point p, and the GraphicView is scrolled to make the Graphic
- * visible.
- */
- {
- return [self placeGraphic:[[Image allocFromZone:[self zone]] initFromStream:stream allowAlpha:alphaOk] at:location];
- }
-
-
- /* Writing data in different forms (other than the internal Draw format) */
-
- - writePSToStream:(NXStream *)stream
- /*
- * Writes out the PostScript generated by drawing all the objects in the
- * glist. The bounding box of the generated encapsulated PostScript will
- * be equal to the bounding box of the objects in the glist (not the
- * bounds of the view).
- */
- {
- NXRect bbox;
-
- if (stream) {
- [self getBBox:&bbox of:glist];
- [self copyPSCodeInside:&bbox to:stream];
- }
-
- return self;
- }
-
- - writeTIFFToStream:(NXStream *)stream
- /*
- * Images all of the objects in the glist and writes out the result in
- * the Tagged Image File Format (TIFF). The image WILL have alpha in it.
- */
- {
- int size;
- NXRect sbounds;
- NXImageInfo image;
- unsigned char *data;
- id cache, savedslist;
-
- if (!stream) return self;
-
- savedslist = slist;
- slist = glist;
- [self cacheSelection];
- [self getBBox:&sbounds of:slist];
- slist = savedslist;
- cache = [[self selectionCache] contentView];
- [cache lockFocus];
- sbounds.origin.x = sbounds.origin.y = 0.0;
- NXSizeBitmap(&sbounds, &size, &image.width, &image.height,
- &image.bitsPerSample, &image.samplesPerPixel,
- &image.planarConfig, &image.photoInterp);
- NX_MALLOC(data, unsigned char, size);
- NXReadBitmap(&sbounds, image.width, image.height, image.bitsPerSample,
- image.samplesPerPixel, image.planarConfig, image.photoInterp,
- data, data + (size >> 1), NULL, NULL, NULL);
- [cache unlockFocus];
- NXWriteTIFF(stream, &image, data);
- NX_FREE(data);
-
- return self;
- }
-
-
- /* Methods overridden from superclass. */
-
- - sizeTo:(NXCoord)width :(NXCoord)height
- /*
- * Overrides View's sizeTo:: so that the cacheWindow is resized when
- * the View is resized.
- */
- {
- if (width != bounds.size.width || height != bounds.size.height) {
- [super sizeTo:width :height];
- [cacheWindow free];
- cacheWindow = createCache(&bounds.size, [self zone]);
- [self resetGUP];
- [self cache:&bounds];
- }
- return self;
- }
-
- - mouseDown:(NXEvent *)event
- /*
- * This method handles a mouse down.
- *
- * If a current tool is in effect, then the mouse down causes a new
- * Graphic to begin being created. Otherwise, the selection is modified
- * either by adding elements to it or removing elements from it, or moving
- * it. Here are the rules:
- *
- * Tool in effect
- * Shift OFF
- * create a new Graphic which becomes the new selection
- * Shift ON
- * create a new Graphic and ADD it to the current selection
- * Control ON
- * leave creation mode, and start selection
- * Otherwise
- * Shift OFF
- * a. Click on a selected Graphic -> select graphic further back
- * b. Click on an unselected Graphic -> that Graphic becomes selection
- * Shift ON
- * a. Click on a selected Graphic -> remove it from selection
- * b. Click on unselected Graphic -> add it to selection
- * Alternate ON
- * if no affected graphic, causes drag select to select only objects
- * completely contained within the dragged box.
- *
- * Essentially, everything works as one might expect except the ability to
- * select a Graphic which is deeper in the list (i.e. further toward the
- * back) by clicking on the currently selected Graphic.
- *
- * This is a very hairy mouseDown:. Most need not be this scary.
- */
- {
- NXPoint p;
- NXRect eb;
- int i, corner, oldMask;
- id factory, g = nil, startg = nil;
- BOOL shift, control, gotHit = NO, deepHit = NO;
-
- /*
- * You only need to do the following line in a mouseDown: method if
- * you receive this message because one of your subviews gets the
- * mouseDown: and does not respond to it (thus, it gets passed up the
- * responder chain to you). In this case, our editView receives the
- * mouseDown:, but doesn't do anything about it, and when it comes
- * to us, we want to become the first responder.
- *
- * Normally you won't have a subview which doesn't do anything with
- * mouseDown:, in which case, you need only return YES from the
- * method acceptsFirstResponder (see that method below) and will NOT
- * need to do the following makeFirstResponder:. In other words,
- * don't put the following line in your mouseDown: implementation!
- *
- * Sorry about confusing this issue ...
- */
-
- if ([window firstResponder] != self) [window makeFirstResponder:self];
-
- shift = (event->flags & NX_SHIFTMASK) ? YES : NO;
- control = (event->flags & NX_CONTROLMASK) ? YES : NO;
-
- p = event->location;
- [self convertPoint:&p fromView:nil];
-
- oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
-
- factory = [self currentGraphic];
- if (!control && factory) {
- if ([factory isEditable]) { /* if editable, try to edit one */
- i = 0;
- g = [glist objectAt:i++];
- while (g != nil) {
- if ([g isKindOf:factory] && [g hit:&p]) {
- if ([g isSelected]) {
- [g deselect];
- [self cache:[g getExtendedBounds:&eb]];
- [slist removeObject:g];
- if ([g isKindOf:[Group class]]) gvFlags.groupInSlist = checkForGroup(slist);
- DIRTY(YES);
- }
- [g edit:event in:editView];
- break;
- }
- g = [glist objectAt:i++];
- }
- }
- if (!g) { /* not editing or no editable graphic found */
- g = [[factory allocFromZone:[self zone]] init];
- if ([NXApp respondsTo:@selector(inspectorPanel)]) {
- [[[NXApp inspectorPanel] delegate] initializeGraphic:g];
- }
- if ([g create:event in:self]) {
- if (!shift) [self deselectAll:self];
- [glist insertObject:g at:0];
- if ([g isSelected]) [slist insertObject:g at:0];
- gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
- [self cacheGraphic:g];
- [g edit:NULL in:editView];
- DIRTY(YES);
- } else {
- [g free];
- }
- }
- } else { /* selecting/resizing/moving */
- i = 0;
- g = [glist objectAt:i++];
- while (g != nil && !gotHit) {
- corner = [g knobHit:&p];
- if (corner > 0) { /* corner hit */
- gotHit = YES;
- [g resize:event by:corner in:self];
- DIRTY(YES);
- } else if (corner) { /* complete miss */
- g = [glist objectAt:i++];
- } else g = nil; /* non-corner opaque hit */
- }
- i = 0;
- if (!gotHit) g = [glist objectAt:i++];
- while (g && !gotHit && !deepHit) {
- if ([g isSelected] && [g hit:&p]) {
- if (shift) {
- gotHit = YES;
- [g deselect];
- [self cache:[g getExtendedBounds:&eb]];
- [slist removeObject:g];
- if ([g isKindOf:[Group class]]) gvFlags.groupInSlist = checkForGroup(slist);
- DIRTY(YES);
- } else {
- gotHit = [self move:event];
- if (!gotHit) {
- deepHit = ![g isOpaque];
- if (!deepHit) gotHit = YES;
- } else {
- DIRTY(YES);
- }
- }
- }
- g = [glist objectAt:i++];
- }
- startg = g;
- if (!gotHit) do {
- if (!g) {
- i = 0;
- g = [glist objectAt:i++];
- }
- if (![g isSelected] && [g hit:&p]) {
- gotHit = YES;
- if (!shift) {
- [self deselectAll:self];
- [slist addObject:g];
- gvFlags.groupInSlist = [g isKindOf:[Group class]];
- }
- [g select];
- DIRTY(YES);
- if (shift) [self getSelection];
- if (deepHit || ![self move:event]) [self cache:[g getExtendedBounds:&eb]];
- } else {
- g = [glist objectAt:i++];
- }
- } while (!gotHit && g != startg);
-
- if (!gotHit && !deepHit) {
- if (!shift) {
- DIRTY([slist count] > 0);
- [self lockFocus];
- [self deselectAll:self];
- [self unlockFocus];
- }
- [self dragSelect:event];
- }
- }
-
- [window flushWindow];
- [window setEventMask:oldMask];
-
- return self;
- }
-
- - drawSelf:(const NXRect *)rects :(int)rectCount
- /*
- * Draws the GraphicView.
- *
- * If cacheing is on or if NXDrawingStatus != NX_DRAWING, then all the
- * graphics which intersect the specified rectangles will be drawn (and
- * clipped to those rectangles). Otherwise, the specified rectangles
- * are composited to the screen from the off-screen cache.
- */
- {
- NXRect *rp;
- NXRect r, visibleRect;
- int i, j, gstate;
-
- if (rects == NULL) return self;
-
- if (gvFlags.cacheing || NXDrawingStatus != NX_DRAWING) {
- if (NXDrawingStatus == NX_DRAWING) {
- [[cacheWindow contentView] lockFocus];
- PSsetgray(NX_WHITE);
- for (j = (rectCount > 1) ? 1 : 0; j < rectCount; j++) {
- NXRectFill(&rects[j]);
- if (gvFlags.showGrid && gvFlags.grid >= 4) {
- NXRectClip(&rects[j]);
- PSsetlinewidth(0.0);
- PSsetgray(gridGray);
- DPSDoUserPath(gupCoords, gupLength, dps_short,
- gupOps, gupLength >> 1, gupBBox, dps_ustroke);
- PSsetgray(NX_WHITE);
- }
- }
- }
- for (j = (rectCount > 1) ? 1 : 0; j < rectCount; j++) {
- NXRectClip(&rects[j]);
- i = [glist count];
- while (i--) [[glist objectAt:i] draw:&rects[j]];
- }
- [Graphic showFastRectFills];
- if (NXDrawingStatus == NX_DRAWING) [[cacheWindow contentView] unlockFocus];
- }
-
- if (!gvFlags.cacheing && NXDrawingStatus == NX_DRAWING) {
- gstate = [cacheWindow gState];
- [self getVisibleRect:&visibleRect];
- for (j = 0; j < rectCount; j++) {
- rp = &r;
- r = rects[j];
- if (!NXEqualRect(rp, &visibleRect)) rp = NXIntersectionRect(&visibleRect, rp);
- if (rp) {
- PScomposite(NX_X(rp), NX_Y(rp), NX_WIDTH(rp), NX_HEIGHT(rp), gstate,
- NX_X(rp), NX_Y(rp), NX_SOVER);
- }
- }
- }
-
- return self;
- }
-
- - keyDown:(NXEvent *)event
- /*
- * Handles one of the arrow keys being pressed.
- * Note that since it might take a while to actually move the selection
- * (if it is large), we check to see if a bunch of arrow key events have
- * stacked up and move them all at once.
- */
- {
- NXPoint p;
- NXEvent e;
- NXCoord delta;
- BOOL gotOne, first;
- NXEvent* eptr = event;
-
- if ((event->data.key.charSet != NX_ASCIISET ||
- event->data.key.charCode != 127) &&
- (event->data.key.charSet != NX_SYMBOLSET ||
- (event->data.key.charCode != LEFTARROW &&
- event->data.key.charCode != RIGHTARROW &&
- event->data.key.charCode != DOWNARROW &&
- event->data.key.charCode != UPARROW))) {
- return [super keyDown:event];
- }
-
- if (event->data.key.charSet == NX_ASCIISET) return [self delete:self];
-
- p.x = p.y = 0.0;
- delta = KeyMotionDeltaDefault;
- delta = floor(delta / GRID) * GRID;
- delta = MAX(delta, GRID);
-
- first = YES;
- do {
- gotOne = NO;
- if (eptr->data.key.charSet == NX_SYMBOLSET) {
- switch (eptr->data.key.charCode) {
- case LEFTARROW:
- p.x -= delta;
- gotOne = YES;
- break;
- case RIGHTARROW:
- p.x += delta;
- gotOne = YES;
- break;
- case UPARROW:
- p.y += delta;
- gotOne = YES;
- break;
- case DOWNARROW:
- p.y -= delta;
- gotOne = YES;
- break;
- default:
- break;
- }
- }
- if (eptr && gotOne && !first) [NXApp getNextEvent:NX_KEYDOWNMASK];
- first = NO;
- } while (gotOne && (eptr = [NXApp peekNextEvent:NX_KEYDOWNMASK into:&e]));
-
- if (p.x || p.y) {
- [self graphicsPerform:@selector(moveBy:) with:(id)&p andDraw:YES];
- [[self window] flushWindow];
- NXPing();
- }
-
- return self;
- }
-
-
- - copySelectionAsPS:(NXStream *)stream
- {
- id savedglist;
-
- if (stream && [slist count]) {
- savedglist = glist;
- glist = slist;
- [self writePSToStream:stream];
- glist = savedglist;
- } else {
- return nil;
- }
-
- return self;
- }
-
- - copySelectionAsTIFF:(NXStream *)stream
- {
- id savedglist;
-
- if (stream && [slist count]) {
- savedglist = glist;
- glist = slist;
- [self writeTIFFToStream:stream];
- glist = savedglist;
- } else {
- return nil;
- }
-
- return self;
- }
-
- - copySelectionToStream:(NXStream *)stream
- {
- NXTypedStream *ts;
-
- if ([slist count]) {
- ts = NXOpenTypedStream(stream, NX_WRITEONLY);
- NXWriteRootObject(ts, slist);
- NXCloseTypedStream(ts);
- } else {
- return nil;
- }
-
- return self;
- }
-
- /*
- * Target/Action methods.
- */
-
- /* Copying the selection to a stream. */
- /*
- * These two method set and get the factory object used to create new
- * Graphic objects (i.e. the subclass of Graphic to use).
- * They are kind of weird since they check to see if the
- * Application object knows what the current graphic is. If it does, then
- * it lets it handle these methods. Otherwise, it determines the
- * current graphic by querying the sender to find out what its title is
- * and converts that title to the name to a factory object. This allows
- * the GraphicView to stand on its own, but also use an application wide
- * tool palette if available.
- * If the GraphicView handles the current graphic by itself, it does so
- * by querying the sender of setCurrentGraphic: to find out its title.
- * It assumes, then, that that title is the name of the factory object to
- * use and calls objc_getClass() to get a pointer to it.
- * If the application is not control what our current graphic is, then
- * we restrict creations to be made only when the control key is down.
- * Otherwise, it is the other way around (control key leaves creation
- * mode). This is due to the fact that the application can be smart
- * enough to set appropriate cursors when a tool is on. The GraphicView
- * can't be.
- */
-
- - currentGraphic
- {
- if ([NXApp respondsTo:@selector(currentGraphic)]) {
- return [NXApp currentGraphic];
- } else {
- return currentGraphic;
- }
- }
-
- - setCurrentGraphic:sender
- {
- currentGraphic = objc_getClass([[sender selectedCell] title]);
- return self;
- }
-
- /* Pasteboard-related target/action methods */
-
- - delete:sender
- {
- int i;
-
- i = [slist count];
- if (i > 0) {
- [self graphicsPerform:@selector(deactivate) andDraw:YES];
- [self graphicsPerform:@selector(activate) andDraw:NO];
- DIRTY(YES);
- while (i--) [glist removeObject:[slist objectAt:i]];
- if (originalPaste == [slist objectAt:0]) {
- [slist removeObjectAt:0];
- gvFlags.freeOriginalPaste = YES;
- }
- [slist freeObjects];
- [slist free];
- slist = [[List allocFromZone:[self zone]] init];
- gvFlags.groupInSlist = NO;
- [window flushWindow];
- }
-
- return self;
- }
-
- - cut:sender
- /*
- * Calls copy: then delete:.
- */
- {
- if ([slist count] > 0) {
- [self copy:sender];
- [self delete:sender];
- consecutivePastes = 0;
- return self;
- } else {
- return nil;
- }
-
- }
-
- - copy:sender
- {
- if ([slist count]) {
- [self copyToPasteboard:[Pasteboard new]];
- lastPastedChangeCount = [[Pasteboard new] changeCount];
- consecutivePastes = 1;
- if (gvFlags.freeOriginalPaste) [originalPaste free];
- gvFlags.freeOriginalPaste = NO;
- originalPaste = [slist objectAt:0];
- }
- return self;
- }
-
- static BOOL includesType(NXAtom *types, NXAtom type)
- {
- while (types && *types) {
- if (type == *types) return YES;
- types++;
- }
- return NO;
- }
-
- - copyToPasteboard:pboard types:(NXAtom *)typesList
- /*
- * Puts all the objects in the slist into the Pasteboard by archiving
- * the slist itself. Also registers the PostScript and TIFF types since
- * the GraphicView knows how to convert its internal type to PostScript
- * or TIFF via the copy{PS,TIFF}ToPasteboardFrom: methods.
- */
- {
- char *data;
- NXStream *stream;
- const char *types[3];
- int i = 0, length, maxlen;
-
- if ([slist count]) {
- types[i++] = DrawPboardType;
- if (!typesList || includesType(typesList, NXPostScriptPboardType)) {
- types[i++] = NXPostScriptPboardType;
- }
- if (!typesList || includesType(typesList, NXTIFFPboardType)) {
- types[i++] = NXTIFFPboardType;
- }
- stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
- [self copySelectionToStream:stream];
- NXGetMemoryBuffer(stream, &data, &length, &maxlen);
- [pboard declareTypes:types num:i owner:[self class]];
- [pboard writeType:DrawPboardType data:data length:length];
- NXCloseMemory(stream, NX_FREEBUFFER);
- return self;
- } else {
- return nil;
- }
- }
-
- - copyToPasteboard:pboard
- {
- return [self copyToPasteboard:pboard types:NULL];
- }
-
- - paste:sender
- /*
- * Pastes from the normal pasteboard.
- * This paste implements "smart paste" which goes like this: if the user
- * pastes in a single item (a Group is considered a single item), then
- * pastes that item again and moves that second item somewhere, then
- * subsequent pastes will be positioned at the same offset between the
- * first and second pastes (this is also known as "transform again").
- */
- {
- id g, pblist;
- NXPoint offset;
- NXAtom type;
- NXRect originalBounds, secondBounds;
- static id secondPaste;
- static NXPoint pasteOffset;
-
- if (!(pblist = [self pasteFromPasteboard:[Pasteboard new]])) return nil;
-
- g = [pblist objectAt:0];
-
- if ([g isKindOf:[Image class]]) {
- [pblist free];
- return self;
- }
-
- type = drawPasteType([[Pasteboard new] types]);
- if (type == DrawPboardType) {
- if (lastPastedChangeCount != [[Pasteboard new] changeCount]) {
- consecutivePastes = 0;
- lastPastedChangeCount = [[Pasteboard new] changeCount];
- if (gvFlags.freeOriginalPaste) [originalPaste free];
- gvFlags.freeOriginalPaste = NO;
- originalPaste = g;
- } else {
- if (consecutivePastes == 1) { /* smart paste */
- pasteOffset.x = 10.0;
- pasteOffset.y = -10.0;
- secondPaste = g;
- } else if ((consecutivePastes==2) && [pblist count]==1) {
- [originalPaste getBounds:&originalBounds];
- [secondPaste getBounds:&secondBounds];
- pasteOffset.x = secondBounds.origin.x - originalBounds.origin.x;
- pasteOffset.y = secondBounds.origin.y - originalBounds.origin.y;
- }
- offset.x = pasteOffset.x * consecutivePastes;
- offset.y = pasteOffset.y * consecutivePastes;
- [self graphicsPerform:@selector(moveBy:) with:&offset andDraw:NO];
- }
- consecutivePastes++;
- [self recacheSelection];
- }
-
- [pblist free];
-
- return self;
- }
-
- - pasteFromPasteboard:pboard
- /*
- * Pastes any type available from the specified Pasteboard into the GraphicView.
- * If the type in the Pasteboard is the internal type, then the objects
- * are simply added to the slist and glist. If it is PostScript or TIFF,
- * then an Image object is created using the contents of
- * the Pasteboard. Returns a list of the pasted objects (which should be freed
- * by the caller).
- */
- {
- char *data;
- int i, length;
- NXAtom type;
- NXStream *stream;
- NXTypedStream *ts;
- id g = nil, pblist = nil;
-
- type = drawPasteType([pboard types]);
- if (type) {
- [self deselectAll:self];
- [pboard readType:type data:&data length:&length];
- stream = NXOpenMemory(data, length, NX_READONLY);
- if (type == DrawPboardType) {
- ts = NXOpenTypedStream(stream, NX_READONLY);
- pblist = NXReadObject(ts);
- i = [pblist count];
- if (i) {
- DIRTY(YES);
- [self deselectAll:self];
- while (i--) {
- g = [pblist objectAt:i];
- [slist insertObject:g at:0];
- [glist insertObject:g at:0];
- gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
- }
- } else {
- [pblist free];
- pblist = nil;
- }
- NXCloseTypedStream(ts);
- NXCloseMemory(stream, NX_FREEBUFFER);
- } else {
- if (type == NXPostScriptPboardType || type == NXTIFFPboardType) {
- g = [self loadImageFromStream:stream at:NULL allowAlpha:(type == NXTIFFPboardType)];
- if (g) {
- pblist = [[List allocFromZone:[self zone]] initCount:1];
- [pblist addObject:g];
- }
- NXCloseMemory(stream, NX_SAVEBUFFER);
- } else {
- NXCloseMemory(stream, NX_FREEBUFFER); // shouldn't happen!
- }
- }
- }
-
- return pblist;
- }
-
- /* Other target/action methods */
-
- - selectAll:sender
- /*
- * Selects all the items in the glist.
- */
- {
- id g;
- int i;
- NXRect visibleRect;
-
- i = [glist count];
- if (!i) return self;
-
- DIRTY(YES);
- [slist free];
- slist = [[List allocFromZone:[self zone]] init];
- [[cacheWindow contentView] lockFocus];
- while (i--) {
- g = [glist objectAt:i];
- if (![g isLocked]) {
- [g select];
- [g draw:NULL];
- [slist insertObject:g at:0];
- gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
- }
- }
- [Graphic showFastRectFills];
- [[cacheWindow contentView] unlockFocus];
- [self getVisibleRect:&visibleRect];
- if ([self canDraw]) {
- [self lockFocus];
- [self drawSelf:&visibleRect :1];
- [self unlockFocus];
- }
- if (sender != self) [window flushWindow];
-
- return self;
- }
-
- - deselectAll:sender
- /*
- * Deselects all the items in the slist.
- */
- {
- NXRect sbounds;
-
- if ([slist count] > 0) {
- DIRTY(YES);
- [self getBBox:&sbounds of:slist];
- [self graphicsPerform:@selector(deselect) andDraw:NO];
- [self cache:&sbounds];
- [slist free];
- slist = [[List allocFromZone:[self zone]] init];
- gvFlags.groupInSlist = NO;
- if (sender != self) [window flushWindow];
- }
-
- return self;
- }
-
- - lock:sender
- /*
- * Locks all the items in the selection so that they can't be selected
- * or resized or moved. Useful if there are some Graphics which are getting
- * in your way. Undo this with unlock:.
- */
- {
- gvFlags.locked = ([slist count] > 0);
- if (gvFlags.locked) {
- [slist makeObjectsPerform:@selector(lock)];
- [self deselectAll:sender];
- }
- return self;
- }
-
- - unlock:sender
- {
- [glist makeObjectsPerform:@selector(unlock)];
- gvFlags.locked = NO;
- return self;
- }
-
- - bringToFront:sender
- /*
- * Brings each of the items in the slist to the front of the glist.
- * The item in the front of the slist will be the new front element
- * in the glist.
- */
- {
- int i;
-
- i = [slist count];
- if (i) {
- DIRTY(YES);
- while (i--) [glist insertObject:[glist removeObject:[slist objectAt:i]] at:0];
- [self recacheSelection];
- }
-
- return self;
- }
-
- - sendToBack:sender
- {
- int i, count;
-
- count = [slist count];
- if (count > 0) {
- DIRTY(YES);
- for (i = 0; i < count; i++) [glist addObject:[glist removeObject:[slist objectAt:i]]];
- [self recacheSelection];
- }
-
- return self;
- }
-
- - group:sender
- /*
- * Creates a new Group object with the current slist as its member list.
- * See the Group class for more info.
- */
- {
- id g;
- int i;
- NXRect eb;
-
- i = [slist count];
- if (i > 1) {
- DIRTY(YES);
- while (i--) [glist removeObject:[slist objectAt:i]];
- g = [[Group allocFromZone:[self zone]] initList:slist];
- [glist insertObject:g at:0];
- slist = [[List allocFromZone:[self zone]] init];
- [slist addObject:g];
- gvFlags.groupInSlist = YES;
- [self cache:[g getExtendedBounds:&eb]];
- if (sender != self) [window flushWindow];
- }
-
- return self;
- }
-
-
- - ungroup:sender
- /*
- * Goes through the slist and ungroups any Group objects in it.
- * Does not descend any further than that (i.e. all the Group objects
- * in the slist are ungrouped, but any Group objects in those ungrouped
- * objects are NOT ungrouped).
- */
- {
- id g;
- int i, k;
- NXRect sbounds;
- BOOL found = NO;
-
- [self getBBox:&sbounds of:slist];
- i = [slist count];
- while (i--) {
- g = [slist objectAt:i];
- if ([g isKindOf:[Group class]]) {
- k = [glist indexOf:g];
- [glist removeObjectAt:k];
- found = YES;
- [[g transferSubGraphicsTo:glist at:k] free];
- }
- }
-
- if (found) {
- DIRTY(YES);
- [self cache:&sbounds];
- if (sender != self) [window flushWindow];
- [self getSelection];
- }
-
- return self;
- }
-
- - alignLeftEdges:sender
- {
- int i;
- NXRect rect;
- NXCoord minEdge = 10000.0;
-
- for (i = [slist count]-1; i >= 0; i--) {
- [[slist objectAt:i] getBounds:&rect];
- if (rect.origin.x < minEdge) minEdge = rect.origin.x;
- }
- [self graphicsPerform:@selector(moveLeftEdgeTo:) with:&minEdge andDraw:YES];
- [window flushWindow];
-
- return self;
- }
-
- - alignRightEdges:sender
- {
- int i;
- NXRect rect;
- NXCoord maxEdge = 0.0;
-
- for (i = [slist count]-1; i >= 0; i--) {
- [[slist objectAt:i] getBounds:&rect];
- if (rect.origin.x + rect.size.width > maxEdge) maxEdge = rect.origin.x + rect.size.width;
- }
- [self graphicsPerform:@selector(moveRightEdgeTo:) with:&maxEdge andDraw:YES];
- [window flushWindow];
-
- return self;
- }
-
- - alignBottomEdges:sender
- {
- int i;
- NXRect rect;
- NXCoord minEdge = 10000.0;
-
- for (i = [slist count]-1; i >= 0; i--) {
- [[slist objectAt:i] getBounds:&rect];
- if (rect.origin.y < minEdge) minEdge = rect.origin.y;
- }
- [self graphicsPerform:@selector(moveBottomEdgeTo:) with:&minEdge andDraw:YES];
- [window flushWindow];
-
- return self;
- }
-
- - alignTopEdges:sender
- {
- int i;
- NXRect rect;
- NXCoord maxEdge = 0.0;
-
- for (i = [slist count]-1; i >= 0; i--) {
- [[slist objectAt:i] getBounds:&rect];
- if (rect.origin.y + rect.size.height > maxEdge) maxEdge = rect.origin.y + rect.size.height;
- }
- [self graphicsPerform:@selector(moveTopEdgeTo:) with:&maxEdge andDraw:YES];
- [window flushWindow];
-
- return self;
- }
-
- - alignVerticalCenters:sender
- {
- int i;
- NXRect rect;
- NXCoord minEdge = 10000.0;
-
- for (i = [slist count]-1; i >= 0; i--) {
- [[slist objectAt:i] getBounds:&rect];
- if (rect.origin.x + floor(rect.size.width / 2.0) < minEdge) {
- minEdge = rect.origin.x + floor(rect.size.width / 2.0);
- }
- }
- [self graphicsPerform:@selector(moveHorizontalCenterTo:) with:&minEdge andDraw:YES];
- [window flushWindow];
-
- return self;
- }
-
- - alignHorizontalCenters:sender
- {
- int i;
- NXRect rect;
- NXCoord minEdge = 10000.0;
-
- for (i = [slist count]-1; i >= 0; i--) {
- [[slist objectAt:i] getBounds:&rect];
- if (rect.origin.y + floor(rect.size.height / 2.0) < minEdge) {
- minEdge = rect.origin.y + floor(rect.size.height / 2.0);
- }
- }
- [self graphicsPerform:@selector(moveVerticalCenterTo:) with:&minEdge andDraw:YES];
- [window flushWindow];
-
- return self;
- }
-
- - changeAspectRatio:sender
- {
- [self graphicsPerform:@selector(sizeToNaturalAspectRatio) andDraw:YES];
- [window flushWindow];
- return self;
- }
-
- - alignToGrid:sender
- {
- [self graphicsPerform:@selector(alignToGrid:) with:self andDraw:YES];
- [window flushWindow];
- return self;
- }
-
- - sizeToGrid:sender
- {
- [self graphicsPerform:@selector(sizeToGrid:) with:self andDraw:YES];
- [window flushWindow];
- return self;
- }
-
- - enableGrid:sender
- /*
- * If the tag of the sender is non-zero, then gridding is enabled.
- * If the tag is zero, then gridding is disabled.
- */
- {
- [self setGridEnabled:[sender selectedTag] ? YES : NO];
- return self;
- }
-
- - hideGrid:sender
- /*
- * If the tag of the sender is non-zero, then the grid is made visible
- * otherwise, it is hidden (but still conceivable in effect).
- */
- {
- [self setGridVisible:[sender selectedTag] ? YES : NO];
- return self;
- }
-
- - print:sender
- {
- return [self printPSCode:sender];
- }
-
- /*
- * Target/Action methods to change Graphic parameters from a Control.
- * If the sender is a Matrix, then the selectedRow is used to determine
- * the value to use (for linecap, linearrow, etc.) otherwise, the
- * sender's floatValue or intValue is used (whichever is appropriate).
- * This allows interface builders the flexibility to design different
- * ways of setting those values.
- */
-
- - takeGridValueFrom:sender
- {
- [self setGridSpacing:[sender intValue]];
- return self;
- }
-
- - takeGridGrayFrom:sender
- {
- [self setGridGray:[sender floatValue]];
- return self;
- }
-
- - takeGrayValueFrom:sender
- {
- float value;
-
- value = [sender floatValue];
- [self graphicsPerform:@selector(setGray:) with:&value andDraw:YES];
- [window flushWindow];
-
- return self;
- }
-
- - takeLineWidthFrom:sender
- {
- float value;
-
- value = [sender floatValue];
- [self graphicsPerform:@selector(setLineWidth:) with:&value andDraw:YES];
- [window flushWindow];
-
- return self;
- }
-
- - takeLineJoinFrom:sender
- {
- if ([sender respondsTo:@selector(selectedRow)]) {
- [self graphicsPerform:@selector(setLineJoin:) with:(void *)[sender selectedRow] andDraw:YES];
- } else {
- [self graphicsPerform:@selector(setLineJoin:) with:(void *)[sender intValue] andDraw:YES];
- }
- [window flushWindow];
- return self;
- }
-
- - takeLineCapFrom:sender
- {
- if ([sender respondsTo:@selector(selectedRow)]) {
- [self graphicsPerform:@selector(setLineCap:) with:(void *)[sender selectedRow] andDraw:YES];
- } else {
- [self graphicsPerform:@selector(setLineCap:) with:(void *)[sender intValue] andDraw:YES];
- }
- [window flushWindow];
- return self;
- }
-
- - takeLineArrowFrom:sender
- {
- if ([sender respondsTo:@selector(selectedRow)]) {
- [self graphicsPerform:@selector(setLineArrow:) with:(void *)[sender selectedRow] andDraw:YES];
- } else {
- [self graphicsPerform:@selector(setLineArrow:) with:(void *)[sender intValue] andDraw:YES];
- }
- [window flushWindow];
- return self;
- }
-
- - takeFillValueFrom:sender
- {
- if ([sender respondsTo:@selector(selectedRow)]) {
- [self graphicsPerform:@selector(setFill:) with:(void *)[sender selectedRow] andDraw:YES];
- } else {
- [self graphicsPerform:@selector(setFill:) with:(void *)[sender intValue] andDraw:YES];
- }
- [window flushWindow];
- return self;
- }
-
- - takeFrameValueFrom:sender
- {
- if ([sender respondsTo:@selector(selectedRow)]) {
- [self graphicsPerform:@selector(setFramed:) with:(void *)[sender selectedRow] andDraw:YES];
- } else {
- [self graphicsPerform:@selector(setFramed:) with:(void *)[sender intValue] andDraw:YES];
- }
- [window flushWindow];
- return self;
- }
-
- - takeLineColorFrom:sender
- {
- NXColor color = [sender color];
- [self graphicsPerform:@selector(setLineColor:) with:(void *)&color andDraw:YES];
- [window flushWindow];
- return self;
- }
-
- - takeFillColorFrom:sender
- {
- NXColor color = [sender color];
- [self graphicsPerform:@selector(setFillColor:) with:(void *)&color andDraw:YES];
- [window flushWindow];
- return self;
- }
-
- - takeTextColorFrom:sender
- {
- NXColor color = [sender color];
- [self graphicsPerform:@selector(setGraphicTextColor:) with:(void *)&color andDraw:YES];
- [window flushWindow];
- return self;
- }
-
- - changeFont:sender
- {
- if ([window firstResponder] == self) {
- [self graphicsPerform:@selector(changeFont:) with:sender andDraw:YES];
- [window flushWindow];
- }
- return self;
- }
-
- /* Accepting becoming the First Responder */
-
- - (BOOL)acceptsFirstResponder
- /*
- * GraphicView always wants to become the first responder when it is
- * clicked on in a window, so it returns YES from this method.
- */
- {
- return YES;
- }
-
- /* Printing */
-
- - beginPrologueBBox:(NXRect *)boundingBox creationDate:(char *)dateCreated
- createdBy:(char *)anApplication fonts:(char *)fontNames
- forWhom:(char *)user pages:(int )numPages title:(char *)aTitle
- /*
- * Include the window title as the name of the document when printing.
- */
- {
- char *s;
- char name[MAXPATHLEN+1];
- const char *title = [window title];
-
- strcpy(name, title);
- s = strchr(name, ' ');
- if (s) *s = '\0';
- return [super beginPrologueBBox:boundingBox creationDate:dateCreated
- createdBy:anApplication fonts:fontNames
- forWhom:user pages:numPages title:name];
- }
-
- - beginSetup
- /*
- * Spit out the custom PostScript defs.
- */
- {
- [super beginSetup];
- PSInit();
- return self;
- }
-
- /* Archiver-related methods. */
-
- - awake
- /*
- * After the GraphicView is unarchived, its cache must be created.
- * If we are loading in this GraphicView just to print it, then we need
- * not load up our cache.
- */
- {
- if (!InMsgPrint) {
- cacheWindow = createCache(&bounds.size, [self zone]);
- [self cache:&bounds];
- }
- initClassVars();
- return [super awake];
- }
-
- - write:(NXTypedStream *)stream
- /*
- * Writes out the glist and the flags.
- * No need to write out the slist since it can be regenerated from the glist.
- */
- {
- [super write:stream];
- gvFlags.dirty = NO;
- [window setDocEdited:NO];
- NXWriteTypes(stream, "@sf", &glist, &gvFlags, &gridGray);
- NXWriteObject(stream, editView);
- return self;
- }
-
- - read:(NXTypedStream *)stream
- /*
- * Reads in the glist and the flags, and regenerates the slist from the glist.
- */
- {
- int i;
- id g, newg;
-
- [super read:stream];
- NXReadTypes(stream, "@sf", &glist, &gvFlags, &gridGray);
- for (i = [glist count]-1; i >= 0; i--) {
- g = [glist objectAt:i];
- newg = [g replaceWithImage];
- if (g != newg) {
- if (g) {
- [glist replaceObjectAt:i with:newg];
- } else {
- [glist removeObjectAt:i];
- }
- }
- }
- [self getSelection];
- [self resetGUP];
- if (NXTypedStreamClassVersion(stream, [self name]) < 1) {
- editView = createEditView(self);
- } else {
- editView = NXReadObject(stream);
- }
-
- return self;
- }
-
- /* Methods to deal with being/becoming the First Responder */
-
- /* Validates whether a menu command makes sense now */
-
- - (BOOL)validateCommand:menuCell
- /*
- * Can be called to see if the specified action is valid on this view now.
- * It returns NO if the GraphicView knows that action is not valid now,
- * otherwise it returns YES. Note the use of the Pasteboard change
- * count so that the GraphicView does not have to look into the Pasteboard
- * every time paste: is validated.
- */
- {
- id pb;
- int i, count, gcount;
- SEL action = [menuCell action];
- static BOOL pboardHasPasteableType = NO;
- static int cachedPasteboardChangeCount = 0;
-
- if (action == @selector(bringToFront:)) {
- if ((count = [slist count]) && [glist count] > count) {
- for (i = 0; i < count; i++) {
- if ([slist objectAt:i] != [glist objectAt:i]) {
- return YES;
- }
- }
- }
- return NO;
- } else if (action == @selector(sendToBack:)) {
- if ((count = [slist count]) && (gcount = [glist count]) > count) {
- for (i = 1; i <= count; i++) {
- if ([slist objectAt:count-i] != [glist objectAt:gcount-i]) {
- return YES;
- }
- }
- }
- return NO;
- } else if (action == @selector(group:) ||
- action == @selector(alignLeftEdges:) ||
- action == @selector(alignRightEdges:) ||
- action == @selector(alignBottomEdges:) ||
- action == @selector(alignTopEdges:) ||
- action == @selector(alignVerticalCenters:) ||
- action == @selector(alignHorizontalCenters:)) {
- return([slist count] > 1);
- } else if (action == @selector(ungroup:)) {
- return(gvFlags.groupInSlist && [slist count] > 0);
- } else if (action == @selector(deselectAll:) ||
- action == @selector(lock:) ||
- action == @selector(changeAspectRatio:) ||
- action == @selector(cut:) ||
- action == @selector(copy:)) {
- return([slist count] > 0);
- } else if (action == @selector(alignToGrid:) ||
- action == @selector(sizeToGrid:)) {
- return(GRID > 1 && [slist count] > 0);
- } else if (action == @selector(unlock:)) {
- return gvFlags.locked;
- } else if (action == @selector(selectAll:)) {
- return([glist count] > [slist count]);
- } else if (action == @selector(paste:)) {
- pb = [Pasteboard new];
- count = [pb changeCount];
- if (count != cachedPasteboardChangeCount) {
- cachedPasteboardChangeCount = count;
- pboardHasPasteableType = (drawPasteType([pb types]) != NULL);
- }
- return pboardHasPasteableType;
- } else if (action == @selector(hideGrid:)) {
- if (GRID >= 4) {
- if ([self gridIsVisible]) {
- if (strcmp([menuCell title], "Hide Grid")) {
- [menuCell setTitle:"Hide Grid"];
- [menuCell setTag:0];
- [menuCell setEnabled:NO];
- }
- } else {
- if (strcmp([menuCell title], "Show Grid")) {
- [menuCell setTitle:"Show Grid"];
- [menuCell setTag:1];
- [menuCell setEnabled:NO];
- }
- }
- return YES;
- } else {
- return NO;
- }
- } else if (action == @selector(enableGrid:)) {
- if (gvFlags.grid > 1) {
- if ([self gridIsEnabled]) {
- if (strcmp([menuCell title], "Turn Grid Off")) {
- [menuCell setTitle:"Turn Grid Off"];
- [menuCell setTag:0];
- [menuCell setEnabled:NO];
- }
- } else {
- if (strcmp([menuCell title], "Turn Grid On")) {
- [menuCell setTitle:"Turn Grid On"];
- [menuCell setTag:1];
- [menuCell setEnabled:NO];
- }
- }
- return YES;
- } else {
- return NO;
- }
- } else if (action == @selector(print:)) {
- return([glist count] > 0);
- }
-
- return YES;
- }
-
- /* Useful scrolling routines. */
-
- - scrollGraphicToVisible:graphic
- {
- NXPoint p;
- NXRect eb;
-
- p = bounds.origin;
- NXContainRect([graphic getExtendedBounds:&eb], &bounds);
- p.x -= bounds.origin.x;
- p.y -= bounds.origin.y;
- if (p.x || p.y) {
- [graphic moveBy:&p];
- bounds.origin.x += p.x;
- bounds.origin.y += p.y;
- [self scrollRectToVisible:[graphic getExtendedBounds:&eb]];
- }
-
- return self;
- }
-
- - scrollPointToVisible:(const NXPoint *)point
- {
- NXRect r;
-
- r.origin.x = point->x - 5.0;
- r.origin.y = point->y - 5.0;
- r.size.width = r.size.height = 10.0;
-
- return [self scrollRectToVisible:&r];
- }
-
- @end
-
-